#include "config.h"

#include "ms2sim.h"

char	*disk_usage_trace;

char	*disksim_param;
ecm_uint32	disksim_raid = 1;
ECMBOOL	disksim_enabled;

static LIST_HEAD(dreqs);
static ecm_uint32	n_dreqs;
static ecm_uint64	total_n_read_bytes;
static ecm_uint64	n_read_bytes;
static ecm_uint64	n_max_read_bytes;

static FILE	*fp_disk_usage;

typedef struct {
	ecm_uint64	ts;
	bcache_t	*bcache;
	ecm_uint64	ts_probed;

	/** list item for dreqs */
	struct list_head	list;
} dreq_t;

#ifdef MSM_DEBUG
static const char *
dbg_dreq(dreq_t *dreq)
{
	static char	buf[256];

	if (dreq == NULL)
		return "null";
	ecm_snprintf(buf, 256, "ts:%s %d %d",
		     dbg_timestep(dreq->ts), dreq->bcache->mid, dreq->bcache->bid);
	return buf;
}

#endif

static dreq_t *
create_dreq(bcache_t *bcache, ecm_uint64 ts)
{
	dreq_t		*dreq;

	dreq = (dreq_t *)ecm_malloc(sizeof(dreq_t));
	ECMASSERT(dreq != NULL);

	dreq->ts = ts;
	dreq->bcache = bcache;
	dreq->ts_probed = 0;

	INIT_LIST_HEAD(&dreq->list);

	return dreq;
}

static void
free_dreq(dreq_t *dreq)
{
	ECMASSERT(n_dreqs > 0);

	list_del(&dreq->list);
	n_dreqs--;
	ecm_free(dreq);
}

ecm_uint64
probe_disk_system(ecm_uint64 ts)
{
	dreq_t		*dreq;

	if (list_empty(&dreqs))
		return TS_MAX;

	dreq = list_entry(dreqs.next, dreq_t, list);
	ECMASSERT(dreq->ts <= ts);

	DBG(DBG_DISK, "probe: ts:%s, dreq:[%s]", dbg_timestep(ts), dbg_dreq(dreq));

	if (dreq->ts_probed == 0) {
		if (disksim_enabled && is_stat_started) {
			bcache_t	*bcache = dreq->bcache;

			dreq->ts_probed = invoke_disksim(ts, bcache->mid, bcache->bid);
		}
		else {
			dreq->ts_probed = (ecm_uint32)(disk_blksize * (1000000.0 / disk_BW) + disk_seektime);
		}
	}
	return (ts + dreq->ts_probed);
}

void
schedule_disk_system(ecm_uint64 ts)
{
	dreq_t	*dreq;

	ECMASSERT(!list_empty(&dreqs));

	dreq = list_entry(dreqs.next, dreq_t, list);
	ECMASSERT(dreq->ts <= ts);
	ECMASSERT(dreq->ts_probed > 0);
	ECMASSERT(dreq->ts + dreq->ts_probed <= ts);

	DBG(DBG_DISK, "schedule: ts:%s, dreq:[%s]", dbg_timestep(ts), dbg_dreq(dreq));

	if (is_stat_started) {
		total_n_read_bytes += disk_blksize;
		n_read_bytes += disk_blksize;
	}

	complete_bcache_IO(dreq->bcache, ts);

	free_dreq(dreq);

	if (!list_empty(&dreqs)) {
		/* adjust next dreq */

		dreq = list_entry(dreqs.next, dreq_t, list);
		dreq->ts = ts;
	}
}

void
invoke_disk_IO(bcache_t *bcache, ecm_uint64 ts)
{
	dreq_t	*dreq;

	ECMASSERT(!bcache->valid);

	dreq = create_dreq(bcache, ts);
	ECMASSERT(dreq != NULL);

	list_add_tail(&dreq->list, &dreqs);
	n_dreqs++;
}

void
check_disk_stats(void)
{
	static ecm_uint32	count = 0;

	if (n_max_read_bytes < n_read_bytes)
		n_max_read_bytes = n_read_bytes;

	if (fp_disk_usage != NULL) {
		if (count > 0)
			ecm_fprintf(fp_disk_usage, "%8u %8.3lf\n", count, n_read_bytes / (1024.0 * 1024.0));
		count++;
	}

	n_read_bytes = 0;
}

void
report_disk_result(void)
{
	ecm_double	avg_disk_usage;

	avg_disk_usage = (1.0 * total_n_read_bytes) / stat_duration;

	printlog("Avg Disk Usage:              %s\n", msl_get_bandwidth_str(avg_disk_usage));
	printlog("Max Disk Usage:              %s\n", msl_get_bandwidth_str((ecm_double)n_max_read_bytes));

	if (fp_disk_usage != NULL) {
		ecm_fclose(fp_disk_usage);
		fp_disk_usage = NULL;
	}
}

ECMBOOL
init_disk(void)
{
	if (!ecm_empty_str(disk_usage_trace)) {
		char	*logpath;

		ecm_asprintf(&logpath, "%s_%s", disk_usage_trace, bcache_mgmt_name);
		fp_disk_usage = open_logfile(logpath, NULL);
	}

	return ECM_TRUE;
}

#ifdef NO_DISKSIM

ECMBOOL init_disksim(void)
{
	return ECM_TRUE;
}

ecm_uint64
invoke_disksim(ecm_uint64 ts, ecm_uint32 mid, ecm_uint32 bid)
{
	return 0;
}

#endif
